﻿using System;
using System.Linq;
using System.Runtime.CompilerServices;
using CoreCalc;
using RayDen.Library.Core;
using RayDen.Library.Core.Components;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Types;

namespace RayDen.SmallRT.Components
{
    public class HitInfo
    {
        public Point HitPoint;
        public Normal Normal;
        public int PrimitiveId;
        public int MatId;
        public IWavelengthDependentValue Reflectance;
    }

    public sealed class SceneManager : Singleton<SceneManager>
    {
        public static double epsilon = 1e-1;
        public int w, h;
        public sealed  class Sphere
        {
            public double rad;
            public Point p;
            public RgbSpectrum c;
            public int refl;
            public Sphere(double r_, Point p_, RgbSpectrum c_, int re_)
            {
                rad = r_;
                p = p_;
                c = c_;
                refl = re_;
            }
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public double intersect(ref RayData r)
            { // returns distance
                var op = p - r.Org;
                double t, b = op & r.Dir, det = b * b - (op.Length2()) + rad * rad;
                if (det < 0)
                    return 1e10f;
                else
                    det = Math.Sqrt(det);
                return (t = b - det) > epsilon ? t : ((t = b + det) > epsilon ? t : 1e10f);
            }



            //========

        }

        public static Sphere[] Sph = { // Scene: radius, position, color, material
                    new  Sphere(1e5f, new Point( 1e5f+1,40.8f,81.6f),   new RgbSpectrum(.75f,.25f,.25f),0),//Left
                    new  Sphere(1e5f, new Point(-1e5f+99f,40.8f,81.6f), new RgbSpectrum(.25f,.25f,.75f),0),//Rght
                    new  Sphere(1e5f, new Point(50f,40.8f, 1e5f),       new RgbSpectrum(.75f,.75f,.75f),0),//Back
                    new  Sphere(1e5f, new Point(50f,40.8f,-1e5f+170f),  new RgbSpectrum(),              0),//Frnt
                    new  Sphere(1e5f, new Point(50f, 1e5f, 81.6f),      new RgbSpectrum(.75f,.75f,.75f),0),//Botm
                    new  Sphere(1e5f, new Point(50f,-1e5f+81.6f,81.6f), new RgbSpectrum(.75f,.75f,.75f),0),//Top
                    new  Sphere(16.5, new Point(27f,16.5f,47f),         new RgbSpectrum(1,1,1)*.999f,   1),//Mirr
                    new  Sphere(16.5, new Point(73f,16.5f,88f),         new RgbSpectrum(1,1,1)*.999f,   2),//Glas
                    new  Sphere(8.5,  new Point(50f,82.5f,60f),          new RgbSpectrum(1,1,1)*.999f,   3)//Light
        };

        public IWavelengthDependentValue[] Reflectances;

        private Float4[] sph_d =
        {
            new Float4(1e5f+1,40.8f,81.6f, 1e5f), 
            new Float4(-1e5f+99f,40.8f,81.6f, 1e5f), 
            new Float4(50f,40.8f, 1e5f, 1e5f), 
            new Float4(50f,40.8f,-1e5f+170f, 1e5f), 
            new Float4(50f, 1e5f, 81.6f, 1e5f), 
            new Float4(50f,-1e5f+81.6f,81.6f, 1e5f), 
            new Float4(27f,16.5f,47f, 16.5f), 
            new Float4(73f,16.5f,88f, 16.5f), 
            new Float4(50f,82.5f,60f, 8.5f), 

        };
        private readonly object lc = new object();

        public SceneManager()
        {
            if (Reflectances == null)
            {
                lock (lc)
                {
                    Reflectances = new IWavelengthDependentValue[SceneManager.Sph.Length];
                    for (int i = 0; i < SceneManager.Sph.Length; i++)
                    {
                        Reflectances[i] = SceneManager.Sph[i].c;
                    }
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Intersect(ref RayData r, ref double t, ref int id)
        {
            int n = 9;
            double d, inf = 1e10;
            t = inf;
            //float[] dists = null;
            //SSE.SSE_Spheres_Intersect(9, ref r.Org, ref r.Dir, sph_d, out dists);
            for (int i = 0; i < n; i++)
            {
                d =
                    //dists[i];
                    Sph[i].intersect(ref r);
                if (d >= t) continue;
                t = d; id = i;
            }
            return t < inf && t < r.maxT;
        }



        public bool Intersect(ref RayData r, ref HitInfo hit)
        {
            if (hit == null)
            {
                hit = new HitInfo();
            }
            int n = 9;
            double d, inf = 1e10;
            var t = inf;
            //float[] dists = null;
            //SSE.SSE_Spheres_Intersect(9, ref r.Org, ref r.Dir, sph_d, out dists);
            for (int i = 0; i < n; i++)
            {
                d =
                    //dists[i];
                    Sph[i].intersect(ref r);
                if (d >= t) continue;
                t = d; hit.PrimitiveId = i;
            }
            bool isHit =  t < inf;
            if (isHit)
            {
                hit.MatId = Sph[hit.PrimitiveId].refl;
                hit.HitPoint = r.Point((float) t);
                hit.Normal = (Normal) (hit.HitPoint - Sph[hit.PrimitiveId].p).Normalize();
            }
            return isHit;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Intersect(ref RayData r)
        {
            int n = 9;
            double d, inf = 1e10;
            double t = inf;
            //float[] dists = null;
            //SSE.SSE_Spheres_Intersect(9, ref r.Org, ref r.Dir, sph_d, out dists);
            for (int i = 0; i < n; i++)
            {
                d = Sph[i].intersect(ref r);
                if (d >= t) continue;
                t = d;
                break;
            }
            return t < inf;
        }
        public void GenerateShadowRay(float u0, float u1, ref Point p, ref RayData r)
        {
            Vector c;
            Geometry.RandomVector(u0,u1, out c);
            r.Org = p;
            var v = (Sph[8].p + c*(float)Sph[8].rad)+0.1f;
            r.Dir = (p-v).Normalize();
        }

        public void GenerateShadowRay(float u0, float u1, ref Point p, ref RayData r,out float res)
        {
            Vector c;
            Geometry.RandomVector(u0, u1, out c);
            r.Org = p;
            var v = (Sph[8].p + c * (float)Sph[8].rad) + 0.1f;
            var l = p - v;
             res = l.Length;
            r.Dir = (l).Normalize();
        }

        public void GenerateCameraRay(double x, double y, out RayData ray)
        {
            RayData cam = new RayData(new Point(50f, 48f, 295.6f), new Vector(0, -0.042612f, -.999f));
            Vector cx = new Vector(w * .5135f / h, 0, 0), cy = (cx ^ cam.Dir).Normalize() * .5135f;

            Vector d = cx * (float) ((x + 0.5) / w - 0.5) + cy * (float) (-(y + 0.5) / h + 0.5) + cam.Dir;
            ray = new RayData(cam.Org + d * 140f, d.Normalize());
        }

    }
}
